Skip to content

feat(voice): 通用语音转写插件桥接框架#1607

Open
MomiJiSan wants to merge 2 commits into
Project-N-E-K-O:mainfrom
MomiJiSan:feat/voice-transcript-bridge
Open

feat(voice): 通用语音转写插件桥接框架#1607
MomiJiSan wants to merge 2 commits into
Project-N-E-K-O:mainfrom
MomiJiSan:feat/voice-transcript-bridge

Conversation

@MomiJiSan
Copy link
Copy Markdown
Contributor

@MomiJiSan MomiJiSan commented Jun 2, 2026

摘要

新增插件无关的通用语音转写桥接框架,允许任意插件通过 @custom_event(event_type="voice_transcript") 订阅实时语音转写事件,并参与多插件仲裁(noop / cancel_response / prime_context)。

这是 #1546 拆分后的基础设施部分,不包含任何伴学插件代码。

架构层次

文件 职责
传输 main_logic/agent_event_bus.py 跨进程 ZMQ 请求/响应 + Future 等待 + 重试
分发 plugin/server/.../dispatch_service.py 动态 handler 发现 + 并发 fan-out + 参数隔离
仲裁 plugin/server/.../event_contracts.py 优先级仲裁(cancel > prime > noop)
桥接 plugin/server/.../voice_transcript_bridge.py 事件规范化 + 分发编排
会话集成 main_logic/core.py 动作执行 + 会话快照守卫
路由 app/agent_server.py + app/main_server.py 生命周期门控 + 结果回传

变更文件

13 文件,+1673/-6 行。零 plugin/plugins/study_companion/ 文件。

测试

53/53 通过 — 覆盖分发、仲裁、桥接、事件总线、agent 集成、core 合约。

Summary by CodeRabbit

发布说明

  • 新增功能
    • 增强语音转录处理:支持插件在语音输入时介入,可取消当前响应或注入上下文,改善语音交互体验
    • 新增自定义事件订阅与仲裁:多插件响应按优先级/策略仲裁单一结果
    • 提升语音桥接的可靠性:更稳健的请求/响应回填与超时处理
  • 改进
    • 优化主服务事件分发与路由导入顺序,提升事件处理鲁棒性
  • 测试
    • 增补大量单元测试,覆盖语音桥接与订阅分发流程

Add a plugin-agnostic voice transcript bridge that allows any plugin to
subscribe to voice_transcript custom events and arbitrate between noop,
cancel_response, and prime_context actions.

Infrastructure changes:
- event_contracts.py: voice transcript action constants, arbitration logic
- dispatch_service.py: concurrent multi-plugin dispatch with per-plugin
  timeout isolation and args separation
- voice_transcript_bridge.py: bridge constants, normalization, resolve entry
- agent_event_bus.py: waiter table, reliable publish/notify with retry
- core.py: _dispatch_voice_transcript_bridge with session snapshot guards
- agent_server.py: _handle_voice_transcript_request background task
- main_server.py: voice_bridge_result event routing

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d741078-daf0-4332-9669-232b1ffd1bd6

📥 Commits

Reviewing files that changed from the base of the PR and between 30c2663 and 36f6a2c.

📒 Files selected for processing (10)
  • app/agent_server.py
  • app/main_server.py
  • main_logic/core.py
  • plugin/server/application/plugins/dispatch_service.py
  • plugin/server/application/plugins/event_contracts.py
  • plugin/tests/unit/server/test_plugin_dispatch_service.py
  • plugin/tests/unit/server/test_plugin_voice_transcript_bridge.py
  • tests/unit/test_agent_server_voice_bridge.py
  • tests/unit/test_core_game_route_memory_contract.py
  • tests/unit/test_main_server_voice_bridge_event.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • plugin/tests/unit/server/test_plugin_voice_transcript_bridge.py
  • plugin/server/application/plugins/event_contracts.py
  • app/agent_server.py
  • main_logic/core.py
  • plugin/tests/unit/server/test_plugin_dispatch_service.py
  • plugin/server/application/plugins/dispatch_service.py

Walkthrough

本次PR实现语音转录插件桥接:在事件总线建立可靠请求/等待、新增插件订阅式分发与仲裁契约、将桥接集成到核心转录处理并由agent_server/main_server完成事件回填喵。

Changes

语音转录事件桥接与插件仲裁

Layer / File(s) Summary
事件总线可靠通信
main_logic/agent_event_bus.py
新增waiters映射、解析中集合与锁;实现notify_voice_bridge_result用于从agent回填结果;实现publish_voice_transcript_request_reliably用于可靠发送voice_transcript_request并等待返回喵。
插件事件契约与仲裁规则
plugin/server/application/plugins/event_contracts.py
定义voice_transcript动作常量与rank、实现候选归一化、priority协商与胜出者选取;在无有效结果时返回聚合noop或默认noop喵。
插件订阅分发与仲裁触发
plugin/server/application/plugins/dispatch_service.py
新增处理器key解析与匹配查找,实现trigger_custom_event_subscribers并发触发每个插件主机、收集结构化结果;新增trigger_arbitrated_custom_event调用仲裁并返回单一结果喵。
语音转录桥接入口
plugin/server/application/plugins/voice_transcript_bridge.py
新增dispatch超时常量、voice_transcript_noop、输入校验与参数组装;实现resolve_voice_transcript_request在文本有效时调用分发服务并返回仲裁结果喵。
核心业务逻辑集成
main_logic/core.py
新增_dispatch_voice_transcript_bridge向插件请求动作并对会话快照做失效保护;在handle_input_transcript的语音分支中优先调用桥接,cancel_response会提前返回以跳过后续用户上下文流程喵。
服务层事件处理与路由
app/agent_server.py, app/main_server.py
agent_server新增异步_handle_voice_transcript_request并按条件回填voice_bridge_result;main_server新增voice_bridge_result分支并调用notify_voice_bridge_result;同时修正_check_agent_api_gate()is_free_version来源与局部router导入位置调整喵。
插件分发测试
plugin/tests/unit/server/test_plugin_dispatch_service.py
新增多种Host桩与测试用例,校验订阅匹配、并发执行、参数隔离、错误隔离、timeout让渡与仲裁返回包含来源字段喵。
契约与桥接测试
plugin/tests/unit/server/test_plugin_event_contracts.py, plugin/tests/unit/server/test_plugin_voice_transcript_bridge.py
验证仲裁决策、priority解析、空文本noop行为、以及分发入参归一化与timeout传递喵。
事件总线可靠通信测试
tests/unit/test_voice_bridge_event_bus.py
验证waiter完成时序、事件循环关闭时取消、publish的重试与waiters清理流程喵。
服务与核心集成测试
tests/unit/test_agent_server_voice_bridge.py, tests/unit/test_core_game_route_memory_contract.py, tests/unit/test_main_server_voice_bridge_event.py
验证agent_server各分支返回noop原因、核心会话快照下副作用抑制与恢复路径、main_server对voice_bridge_result的校验与通知喵。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

语音飘过小程序,插件们来表态喵,
Future 等待不慌张,仲裁挑胜者来喵,
会话快照把旧事忘,响应或注入又飞扬喵,
桥接通了端到端,测试护航心不慌喵,
喵~ 功能上线快乐,大家一起喵✨🎙️

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地概括了核心变更——实现了通用语音转写插件桥接框架,直接对应 PR 的主要目标和变更内容。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30c266394d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/main_server.py
Comment on lines 1781 to 1782
app.include_router(game_router)
app.include_router(card_assist_router)
app.include_router(capture_router)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-include the card-assist router

In this router registration list, card_assist_router is no longer included, so the existing frontend calls to /api/card-assist/clarify, /generate, /refine, and /chat in static/js/character_card_manager.js will hit 404 in the main app even though main_routers/card_assist_router.py still defines those endpoints. This removes the character-card assistant flow for users opening the companion UI.

Useful? React with 👍 / 👎.

Comment thread app/agent_server.py Outdated
Comment on lines +1344 to +1345
ok, reasons = cm.is_agent_api_ready()
# 字段名保留 is_free_version(前端/下游 gate 消费者沿用),值取 agent 维度的
# is_agent_free():判 agent 是否走内置免费模型,而非 core/assist 的版本免费。
return {"ready": ok, "reasons": reasons, "is_free_version": cm.is_agent_free()}
return {"ready": ok, "reasons": reasons, "is_free_version": cm.is_free_version()}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore agent-scoped free flag

When the voice/core provider is free but the Agent model is a paid/custom model, this now reports is_free_version as true because is_free_version() is tied to the core/voice setting, while the surrounding agent gate consumers use this field for Agent-model quota/prompt behavior. ConfigManager.is_agent_free() is the agent-model truth source, so this regression can incorrectly show/free-gate the Agent path whenever core is free and agent is not.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
tests/unit/test_agent_server_voice_bridge.py (1)

10-174: ⚡ Quick win

漏了两条分支没测喵~

_handle_voice_transcript_request 一共有四个 noop 分支,但这里只覆盖了 agent_disabledplugin_lifecycle_start_failed 两个喵。empty_transcript(转写为空)和 user_plugin_disabledagent_flags["user_plugin_enabled"] 为 False)这两条路径还没人守着,哪天有人改了判断顺序,笨蛋你都察觉不到的说~要不要本喵帮你补上这两条用例喵?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_agent_server_voice_bridge.py` around lines 10 - 174, Add two
missing unit tests for _handle_voice_transcript_request: one where transcript is
empty (expect emitted payload result
{"action":"noop","reason":"empty_transcript"}, ensure
resolve_voice_transcript_request not called) and one where
Modules.agent_flags["user_plugin_enabled"] is False (expect emitted payload
result {"action":"noop","reason":"user_plugin_disabled"}, ensure resolve not
called); follow existing test patterns (monkeypatch
srv.Modules.analyzer_enabled/plugin_lifecycle_started, monkeypatch
srv._emit_main_event to capture emitted, monkeypatch
voice_transcript_bridge.resolve_voice_transcript_request to track calls) and
assert emitted["payload"]["event_id"] matches input event_id and resolve was not
invoked.
plugin/tests/unit/server/test_plugin_dispatch_service.py (1)

178-219: ⚡ Quick win

给未就绪插件补一条“不会触发 host 调用”的断言。

这里现在只校验了 PLUGIN_NOT_READY,但没有锁住“健康检查失败后必须短路、不再调用 trigger_custom_event”这个契约喵。plugin/server/application/plugins/dispatch_service.py:167-183 明确是在 health_check 失败后直接抛错;如果后续回归成对死插件仍发请求,这个用例还是会继续通过喵。

可以直接补上的断言喵
     assert results[0]["plugin_id"] == "alpha"
     assert results[0]["event_id"] == "handle_transcript"
     assert results[0]["success"] is False
     assert results[0]["code"] == "PLUGIN_NOT_READY"
+    assert stopped_host.calls == []
     assert results[1] == {
         "plugin_id": "beta",
         "event_id": "handle_transcript",
         "success": True,
         "result": {"action": "cancel_response"},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/tests/unit/server/test_plugin_dispatch_service.py` around lines 178 -
219, Add an assertion to the
test_trigger_custom_event_subscribers_keeps_per_plugin_errors that verifies the
not-ready plugin's host was not invoked after health_check failed: after
creating stopped_host (the _Host instance) and calling
PluginDispatchService().trigger_custom_event_subscribers, assert that
stopped_host.calls is empty (e.g. assert not stopped_host.calls or assert
stopped_host.calls == []) to ensure trigger_custom_event was short-circuited for
that plugin per the behavior in
PluginDispatchService.trigger_custom_event_subscribers.
plugin/tests/unit/server/test_plugin_voice_transcript_bridge.py (1)

23-29: ⚡ Quick win

空转录用例最好顺手锁一下“不会分发”这个副作用喵。

现在只校验了返回值;如果以后有人重构成“先调插件再返回 noop”,这个测试依然会绿喵。考虑注入 _DispatchService(),再断言 calls == [],这样才能把 voice_transcript_bridge.py:34-43 的短路契约一起守住喵。

一个最小改法喵
 `@pytest.mark.asyncio`
 async def test_resolve_voice_transcript_request_returns_noop_for_empty_text() -> None:
+    dispatch_service = _DispatchService()
     result = await voice_transcript_bridge.resolve_voice_transcript_request(
-        {"transcript": "   "}
+        {"transcript": "   "},
+        dispatch_service=dispatch_service,  # type: ignore[arg-type]
     )
 
     assert result == {"action": "noop", "reason": "empty_transcript"}
+    assert dispatch_service.calls == []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/tests/unit/server/test_plugin_voice_transcript_bridge.py` around lines
23 - 29, The test only asserts the noop return but not that no dispatch
happened; modify
test_resolve_voice_transcript_request_returns_noop_for_empty_text to inject or
mock the _DispatchService (the dispatcher used by
voice_transcript_bridge.resolve_voice_transcript_request) and assert its call
list is empty after calling resolve_voice_transcript_request with {"transcript":
"   "}, so you verify both the return value and that the dispatcher's calls ==
[] (preserving the short-circuit contract in resolve_voice_transcript_request).
plugin/server/application/plugins/dispatch_service.py (1)

290-294: 💤 Low value

变量名遮蔽了外层参数喵~

第 292 行的 for plugin_id, event_id in handlers 中的 event_id 遮蔽了外层参数 event_id(第 206 行)。虽然在这个上下文中不会造成 bug,因为外层的 event_id 在这之后没有被使用,但可能会让人困惑喵~ 考虑换个变量名比如 handler_event_id 会更清晰喵!

♻️ 建议的修改喵
         return list(
             await asyncio.gather(
-                *(_dispatch_handler(plugin_id, event_id) for plugin_id, event_id in handlers)
+                *(_dispatch_handler(plugin_id, handler_event_id) for plugin_id, handler_event_id in handlers)
             )
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/server/application/plugins/dispatch_service.py` around lines 290 -
294, The generator loop in the return statement uses "for plugin_id, event_id in
handlers" which shadows the outer "event_id" parameter (see _dispatch_handler
and handlers usage); rename the inner loop variable to something like
"handler_event_id" and update the call to _dispatch_handler(plugin_id,
handler_event_id) to avoid shadowing and improve clarity.
plugin/server/application/plugins/event_contracts.py (2)

106-107: 💤 Low value

_coerce_priority 被重复调用了喵~

candidate.get("priority")_normalize_voice_transcript_candidate 第 56 行已经被 _coerce_priority 处理过了,这里再调用一次是多余的喵。虽然不会造成错误,但可以稍微优化一下喵~

♻️ 建议的修改喵
-        priority = _coerce_priority(candidate.get("priority", 0))
+        priority = candidate["priority"]  # already coerced by _normalize_voice_transcript_candidate
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/server/application/plugins/event_contracts.py` around lines 106 - 107,
在 _normalize_voice_transcript_candidate 中已经对 candidate 的 priority 进行了
_coerce_priority 处理,所以在当前代码块不要再次调用 _coerce_priority(candidate.get("priority",
0)); 直接读取已归一化的 priority 字段(或使用在 _normalize_voice_transcript_candidate
中最终保存的变量)并用该值计算 rank(使用 VOICE_TRANSCRIPT_ACTION_RANK.get(action, 0) 保持不变),去掉重复的
_coerce_priority 调用以消除冗余。

139-143: 💤 Low value

哼,常量定义了不用是要闹哪样喵?

第 140 行用了字符串字面量 "noop" 而不是已经定义好的 VOICE_TRANSCRIPT_ACTION_NOOP 常量喵~ 虽然这里是针对未知事件类型的通用返回,但既然已经定义了常量,保持一致性会更好喵!

♻️ 建议的修改喵
     return {
-        "action": "noop",
+        "action": VOICE_TRANSCRIPT_ACTION_NOOP,
         "reason": "no_event_contract",
         "event_type": event_type,
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/server/application/plugins/event_contracts.py` around lines 139 - 143,
将返回字典中硬编码的字符串 "noop" 替换为已定义的常量 VOICE_TRANSCRIPT_ACTION_NOOP 以保持一致性;在插件的
event_contracts.py 中定位生成该返回值的函数/分支(当前返回 {"action": "noop", "reason":
"no_event_contract", "event_type": event_type})并将 "noop" 替换为
VOICE_TRANSCRIPT_ACTION_NOOP,确保导入或在同一模块中引用该常量以避免未定义错误。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/main_server.py`:
- Around line 611-615: 当前代码在处理 event_type == "voice_bridge_result" 时把非 dict 的
event.get("result") 默认为 {} 然后仍调用 notify_voice_bridge_result,导致畸形 payload
被当作成功完成;请修改该分支:先取 result = event.get("result") 并验证 isinstance(result, dict);若为
dict 则按原样调用 notify_voice_bridge_result(str(event.get("event_id") or ""),
result);若不是 dict 则不要调用 notify_voice_bridge_result,而应记录警告/错误日志(包含 event_id 与实际
payload),并让超时/重试或错误处理路径继续(不要吞掉或替换为 {}),以便事件总线能按预期处理失败或重试。

In `@main_logic/core.py`:
- Around line 1988-2011: The current logic in handle_input_transcript()
conflates session-change detection with consuming the transcript:
_session_changed() returning True causes an early return that drops the
transcript and prevents side effects (sync_message_queue, turn counts, metrics),
and the cancel_response branch returns "cancel_response" even when
cancel_response() wasn't executed or failed. Change the flow so that: 1) if
_session_changed() is True the function returns an empty action (allowing normal
transcript handling to continue) instead of swallowing the input; 2) in the
cancel_response branch call getattr(session_snapshot, "cancel_response", None)
and only return "cancel_response" when cancel_response is callable and executes
successfully; if the method is missing or raises, log the error and fall through
to the normal transcript handling path (do not return the action). Keep
references to _dispatch_voice_transcript_bridge(), session_snapshot vs
self.session, cancel_response, and sync_message_queue when updating the logic.

---

Nitpick comments:
In `@plugin/server/application/plugins/dispatch_service.py`:
- Around line 290-294: The generator loop in the return statement uses "for
plugin_id, event_id in handlers" which shadows the outer "event_id" parameter
(see _dispatch_handler and handlers usage); rename the inner loop variable to
something like "handler_event_id" and update the call to
_dispatch_handler(plugin_id, handler_event_id) to avoid shadowing and improve
clarity.

In `@plugin/server/application/plugins/event_contracts.py`:
- Around line 106-107: 在 _normalize_voice_transcript_candidate 中已经对 candidate 的
priority 进行了 _coerce_priority 处理,所以在当前代码块不要再次调用
_coerce_priority(candidate.get("priority", 0)); 直接读取已归一化的 priority 字段(或使用在
_normalize_voice_transcript_candidate 中最终保存的变量)并用该值计算 rank(使用
VOICE_TRANSCRIPT_ACTION_RANK.get(action, 0) 保持不变),去掉重复的 _coerce_priority
调用以消除冗余。
- Around line 139-143: 将返回字典中硬编码的字符串 "noop" 替换为已定义的常量
VOICE_TRANSCRIPT_ACTION_NOOP 以保持一致性;在插件的 event_contracts.py 中定位生成该返回值的函数/分支(当前返回
{"action": "noop", "reason": "no_event_contract", "event_type": event_type})并将
"noop" 替换为 VOICE_TRANSCRIPT_ACTION_NOOP,确保导入或在同一模块中引用该常量以避免未定义错误。

In `@plugin/tests/unit/server/test_plugin_dispatch_service.py`:
- Around line 178-219: Add an assertion to the
test_trigger_custom_event_subscribers_keeps_per_plugin_errors that verifies the
not-ready plugin's host was not invoked after health_check failed: after
creating stopped_host (the _Host instance) and calling
PluginDispatchService().trigger_custom_event_subscribers, assert that
stopped_host.calls is empty (e.g. assert not stopped_host.calls or assert
stopped_host.calls == []) to ensure trigger_custom_event was short-circuited for
that plugin per the behavior in
PluginDispatchService.trigger_custom_event_subscribers.

In `@plugin/tests/unit/server/test_plugin_voice_transcript_bridge.py`:
- Around line 23-29: The test only asserts the noop return but not that no
dispatch happened; modify
test_resolve_voice_transcript_request_returns_noop_for_empty_text to inject or
mock the _DispatchService (the dispatcher used by
voice_transcript_bridge.resolve_voice_transcript_request) and assert its call
list is empty after calling resolve_voice_transcript_request with {"transcript":
"   "}, so you verify both the return value and that the dispatcher's calls ==
[] (preserving the short-circuit contract in resolve_voice_transcript_request).

In `@tests/unit/test_agent_server_voice_bridge.py`:
- Around line 10-174: Add two missing unit tests for
_handle_voice_transcript_request: one where transcript is empty (expect emitted
payload result {"action":"noop","reason":"empty_transcript"}, ensure
resolve_voice_transcript_request not called) and one where
Modules.agent_flags["user_plugin_enabled"] is False (expect emitted payload
result {"action":"noop","reason":"user_plugin_disabled"}, ensure resolve not
called); follow existing test patterns (monkeypatch
srv.Modules.analyzer_enabled/plugin_lifecycle_started, monkeypatch
srv._emit_main_event to capture emitted, monkeypatch
voice_transcript_bridge.resolve_voice_transcript_request to track calls) and
assert emitted["payload"]["event_id"] matches input event_id and resolve was not
invoked.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d9b9198e-5b41-4477-9681-b525b6719b42

📥 Commits

Reviewing files that changed from the base of the PR and between f8e28f3 and 30c2663.

📒 Files selected for processing (13)
  • app/agent_server.py
  • app/main_server.py
  • main_logic/agent_event_bus.py
  • main_logic/core.py
  • plugin/server/application/plugins/dispatch_service.py
  • plugin/server/application/plugins/event_contracts.py
  • plugin/server/application/plugins/voice_transcript_bridge.py
  • plugin/tests/unit/server/test_plugin_dispatch_service.py
  • plugin/tests/unit/server/test_plugin_event_contracts.py
  • plugin/tests/unit/server/test_plugin_voice_transcript_bridge.py
  • tests/unit/test_agent_server_voice_bridge.py
  • tests/unit/test_core_game_route_memory_contract.py
  • tests/unit/test_voice_bridge_event_bus.py

Comment thread app/main_server.py Outdated
Comment thread main_logic/core.py
@MomiJiSan
Copy link
Copy Markdown
Contributor Author

做了什么

新增插件无关的通用语音转写桥接框架,包含四层:

文件 职责
事件总线 main_logic/agent_event_bus.py 跨进程 ZMQ 请求/响应 + Future 等待器 + 重试 + 清理
插件分发 plugin/server/.../dispatch_service.py 动态 handler 发现 + 并发 fan-out + 参数隔离 + host 超时
仲裁合约 plugin/server/.../event_contracts.py 多插件优先级仲裁:cancel_response > prime_context > noop
桥接入口 plugin/server/.../voice_transcript_bridge.py 事件规范化 + 分发编排

主程序侧(core.py / agent_server.py / main_server.py)负责:

  • 会话快照守卫:桥接 await 期间发生热切换/重连时丢弃过期结果
  • 生命周期门控:analyzer 总开关关闭时不启动插件分发
  • 动作执行:cancel_response 跳过用户上下文副作用,prime_context 注入上下文但不额外创建回复

为什么

#1546 混入了主程序修改和伴学插件修改,reviewer 要求拆分。基础设施部分提取为这个 PR。

从架构上看,实时语音转写需要走插件层仲裁而非硬编码在 main
里——不同插件对同一段语音可能有不同诉求(忽略/打断当前回复/注入学习上下文),需要一套通用的分发+仲裁机制。这套框架与任何具体插件无关,伴学插件的 voice handler 只是其中一个消费者。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant